/*----------------------------------------------------------------------
 *
 * Copyright 2009, Thomas Dejanovic.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 *---------------------------------------------------------------------
 *
 * Low Pin Count Bus Interface module.
 *
 * This is not realy intels LPCB but was inspired by it.
 *
 *---------------------------------------------------------------------*/


module dcom_lpcb_if

  (/*AUTOARG*/
  // Outputs
  tx_data, tx_data_valid, lpcb_frame_out, lpcb_cad_out,
  // Inputs
  clk, clk_en, reset, rx_data, rx_data_valid, rx_error, tx_ready,
  lpcb_frame_in, lpcb_cad_in
  );

  //----------------------------------------
  input                 clk;
  input                 clk_en;

  input                 reset;
  //----------------------------------------

  //----------------------------------------
  // data from the host
  input [7:0]           rx_data;
  input                 rx_data_valid;
  input                 rx_error;

  // data to be sent to the host.
  output [7:0]          tx_data;
  output                tx_data_valid;
  input                 tx_ready;
  //----------------------------------------

  //----------------------------------------
  // lpcb interface
  output                lpcb_frame_out;
  output [3:0]          lpcb_cad_out;

  input                 lpcb_frame_in;
  input [3:0]           lpcb_cad_in;
  //----------------------------------------

  /*----------------------------------------------------------------*/

  /*-AUTOUNUSED-*/

  /*AUTOINPUT*/

  /*AUTOOUTPUT*/

  /*-AUTOREGINPUT-*/

  /*AUTOREG*/

  /*AUTOWIRE*/

  /*------------------------------------------------------------------
   *
   * local definitions and connections.
   *
   * */

`include "lpcb_defines.vh"

  /*-----------------------------------------------------------------
   *
   * need a small Rx fifo.
   *
   * */

  reg                   rx_fifo_pop;
  wire                  rx_fifo_data_valid, rx_fifo_data_valid_int;
  wire [7:0]            rx_fifo_data;

  reg [7:0]             rx_fifo_data_m [0:7];
  reg [2:0]             write_p, read_p;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      rx_fifo_data_m[0] <= 8'd0;
      write_p <= 3'd0;
      read_p  <= 3'd0;
    end else begin
      if (rx_data_valid && (write_p != (read_p - 3'd1))) begin
        rx_fifo_data_m[write_p] <= rx_data;
        write_p <= write_p + 3'd1;
      end

      if (rx_fifo_pop && rx_fifo_data_valid_int) begin
        read_p <= read_p + 3'd1;
      end
    end
  end

  assign rx_fifo_data = rx_fifo_data_m[read_p];
  assign rx_fifo_data_valid_int = (read_p != write_p);
  assign rx_fifo_data_valid     = rx_fifo_data_valid_int & ~rx_fifo_pop;

  /*------------------------------------------------------------------
   *
   * def __init__(self, ser, OpCodes={'read':0x21,'write':0x22},
   * byteOrder='>', format='B', opCodeLen=2, addressLen=3, dataLen=2):
   *
   * 'format' is the format character used by the struct module for
   * packing/unpacking serial data (default 1 unsigned char from int.)
   *
   * 'opCodeLen' is the opcodes' length in bytes.
   *
   * 'addressLen' is the register address' length in bytes.
   *
   * 'dataLen' is the data bus width in bytes.  All data values
   * read/written to the serial port will be of this width.
   *
   *------------------------------------------------------------------
   *
   * main fsm.
   *
   * */

  parameter [4:0] // synopsys enum rx_state
               RX_IDLE           = 4'd0,

               GET_ADDR          = 4'd1,
               GET_WRITE_DATA    = 4'd2,
               SEND_COMMAND      = 4'd3,
               SEND_ADDRESS      = 4'd4,
               SEND_DATA         = 4'd5,

               RX_ABORT          = 4'd6,
               RX_RESET          = 4'd7,

               GET_READ_DATA     = 4'd8,

               RX_PAUSE          = 4'd9,

               END               = 4'd15;


reg [4:0] /* synopsys enum rx_state */ rx_state;

`ifdef IVERILOG
/*AUTOASCIIENUM("rx_state", "ascii_rx_state", nul)*/
// Beginning of automatic ASCII enum decoding
reg [111:0]             ascii_rx_state;         // Decode of rx_state
always @(rx_state) begin
  case ({rx_state})
    RX_IDLE:        ascii_rx_state = "rx_idle       ";
    GET_ADDR:       ascii_rx_state = "get_addr      ";
    GET_WRITE_DATA: ascii_rx_state = "get_write_data";
    SEND_COMMAND:   ascii_rx_state = "send_command  ";
    SEND_ADDRESS:   ascii_rx_state = "send_address  ";
    SEND_DATA:      ascii_rx_state = "send_data     ";
    RX_ABORT:       ascii_rx_state = "rx_abort      ";
    RX_RESET:       ascii_rx_state = "rx_reset      ";
    GET_READ_DATA:  ascii_rx_state = "get_read_data ";
    RX_PAUSE:       ascii_rx_state = "rx_pause      ";
    END:            ascii_rx_state = "end           ";
    default:        ascii_rx_state = "%Error        ";
  endcase
end
// End of automatics
`endif

  //
  //
  // FSM
  //
  //

  reg          command_done;
  reg          read_command_done;
  reg          command_done_set;
  reg          read_command_done_set;
  reg [3:0]    count;
  reg [2:0]    data_count;

  reg [31:0]   address;
  reg [31:0]   write_data;
  reg          write_cmd;
  wire [1:0]   address_range;

  assign       address_range = (|address[31:24]) ? 2'd3 :
                               (|address[23:16]) ? 2'd2 :
                               (|address[15: 8]) ? 2'd1 :
                               2'd0;

  reg          lpcb_frame_out;
  reg [3:0]    lpcb_cad_out;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      rx_state    <= RX_IDLE;
      count       <= 4'd0;
      data_count  <= 3'd0;
      rx_fifo_pop <= 1'd0;

      address     <= 32'd0;
      write_data  <= 32'd0;
      write_cmd   <= 1'd0;

      lpcb_frame_out <= 1'd0;
      lpcb_cad_out   <= 4'd0;

      command_done  <= 1'd0;
      read_command_done  <= 1'd0;
    end else begin
      rx_fifo_pop <= 1'd0;
      lpcb_frame_out <= 1'd0;
      lpcb_cad_out   <= 4'd0;

      command_done      <= command_done | command_done_set;
      read_command_done <= read_command_done | read_command_done_set;
      casez(rx_state)
        RX_IDLE: begin
          count       <= 4'd0;
          data_count  <= 3'd0;
          write_data  <= 32'd0;
          write_cmd   <= 1'd0;
          command_done  <= 1'd0;
          read_command_done  <= 1'd0;
          // address     <= 32'd0;
          if (rx_error) begin
            // do something sane here?
            rx_state <= RX_IDLE;
          end else if (rx_fifo_data_valid) begin
            write_cmd  <= (rx_fifo_data[3:0] == `LPCB_WRITE_SUB_OPCODE) |
                          (rx_fifo_data[3:0] == `LPCB_REPEAT_WRITE_SUB_OPCODE);
            count <= 4'd3; // used to index address bytes.
            rx_fifo_pop <= 1'd1;
            case (rx_fifo_data[3:0])
              `LPCB_READ_SUB_OPCODE         : rx_state <= GET_ADDR;
              `LPCB_WRITE_SUB_OPCODE        : rx_state <= GET_ADDR;
              `LPCB_REPEAT_READ_SUB_OPCODE  : rx_state <= SEND_COMMAND;
              `LPCB_REPEAT_WRITE_SUB_OPCODE : rx_state <= GET_WRITE_DATA;
              `LPCB_ABORT_SUB_OPCODE        : rx_state <= RX_ABORT;
              `LPCB_RESET_SUB_OPCODE        : rx_state <= RX_RESET;
              default                       : rx_state <= RX_IDLE;
            endcase // case(rx_fifo_data[3:0])

            // store the number of data words in this transaction -
            // this is support for block read and write
            // transactions.
            case (rx_fifo_data[7:6])
              2'd0    : data_count  <= 3'd0;
              2'd1    : data_count  <= 3'd1;
              2'd2    : data_count  <= 3'd3;
              2'd3    : data_count  <= 3'd7;
              default : data_count  <= 3'd0;
            endcase // case(rx_fifo_data[7:6])
          end
        end

        GET_ADDR: begin
          if (rx_error) begin
            // do something sane here?
            rx_state   <= RX_IDLE;
          end else if (rx_fifo_data_valid) begin
            rx_fifo_pop <= 1'd1;
            case(count)
              4'd3 : address[31:24] <= rx_fifo_data;
              4'd2 : address[23:16] <= rx_fifo_data;
              4'd1 : address[15: 8] <= rx_fifo_data;
              4'd0 : address[ 7: 0] <= rx_fifo_data;
            endcase // case(count)
            if (count == 4'd0) begin
              rx_state <= write_cmd ? GET_WRITE_DATA : SEND_COMMAND;
              count    <= 4'd3;
            end else begin
              count    <= count - 4'd1;
              rx_state <= GET_ADDR;
            end
          end else
            rx_state   <= GET_ADDR;
        end

        GET_WRITE_DATA: begin
          if (rx_error) begin
            // do something sane here?
            rx_state   <= RX_IDLE;
          end else if (rx_fifo_data_valid) begin
            rx_fifo_pop <= 1'd1;
            case(count)
              4'd3 : write_data[31:24] <= rx_fifo_data;
              4'd2 : write_data[23:16] <= rx_fifo_data;
              4'd1 : write_data[15: 8] <= rx_fifo_data;
              4'd0 : write_data[ 7: 0] <= rx_fifo_data;
            endcase // case(count)
            if (count == 4'd0) begin
              rx_state <= SEND_COMMAND;
            end else begin
              count <= count - 4'd1;
              rx_state <= GET_WRITE_DATA;
            end
          end else
            rx_state <= GET_WRITE_DATA;
        end

        SEND_COMMAND: begin
          lpcb_frame_out <= 1'd1;
          if (write_cmd) begin
            case(address_range)
              2'd0 : lpcb_cad_out <= `LPCB_WRITE_8B_CMD;
              2'd1 : lpcb_cad_out <= `LPCB_WRITE_16B_CMD;
              2'd2 : lpcb_cad_out <= `LPCB_WRITE_24B_CMD;
              2'd3 : lpcb_cad_out <= `LPCB_WRITE_32B_CMD;
            endcase // case(address_range)
          end else begin
            case(address_range)
              2'd0 : lpcb_cad_out <= `LPCB_READ_8B_CMD;
              2'd1 : lpcb_cad_out <= `LPCB_READ_16B_CMD;
              2'd2 : lpcb_cad_out <= `LPCB_READ_24B_CMD;
              2'd3 : lpcb_cad_out <= `LPCB_READ_32B_CMD;
            endcase // case(address_range)
          end
          rx_state <= SEND_ADDRESS;
          count <= {1'd0, address_range, 1'd1};
        end

        SEND_ADDRESS: begin
          lpcb_frame_out <= 1'd0;
          case(count)
            4'd7 : lpcb_cad_out <= address[31:28];
            4'd6 : lpcb_cad_out <= address[27:24];
            4'd5 : lpcb_cad_out <= address[23:20];
            4'd4 : lpcb_cad_out <= address[19:16];
            4'd3 : lpcb_cad_out <= address[15:12];
            4'd2 : lpcb_cad_out <= address[11: 8];
            4'd1 : lpcb_cad_out <= address[ 7: 4];
            4'd0 : lpcb_cad_out <= address[ 3: 0];
            default : lpcb_cad_out <= 4'd0;
          endcase // case(count)
          if (count == 4'd0) begin
            count <= 4'd7;
            if (write_cmd)
              rx_state <= SEND_DATA;
            else
              rx_state <= GET_READ_DATA;
          end else begin
            count <= count - 4'd1;
            rx_state <= SEND_ADDRESS;
          end
        end

        SEND_DATA: begin
          lpcb_frame_out <= 1'd0;
          case(count)
            4'd7 : lpcb_cad_out <= write_data[31:28];
            4'd6 : lpcb_cad_out <= write_data[27:24];
            4'd5 : lpcb_cad_out <= write_data[23:20];
            4'd4 : lpcb_cad_out <= write_data[19:16];
            4'd3 : lpcb_cad_out <= write_data[15:12];
            4'd2 : lpcb_cad_out <= write_data[11: 8];
            4'd1 : lpcb_cad_out <= write_data[ 7: 4];
            4'd0 : lpcb_cad_out <= write_data[ 3: 0];
            default : lpcb_cad_out <= 4'd0;
          endcase // case(count)
          if (count == 4'd0) begin
            if (data_count == 3'd0) begin
              rx_state <= RX_IDLE;
            end else begin
              rx_state   <= RX_PAUSE;
            end
          end else begin
            count <= count - 4'd1;
            rx_state <= SEND_DATA;
          end
        end

        RX_PAUSE: begin
          count    <= 4'd3;
          if (command_done) begin
            rx_state   <= GET_WRITE_DATA;
            data_count <= data_count - 3'd1;
            command_done  <= 1'd0;
            read_command_done  <= 1'd0;
          end else begin
            rx_state   <= RX_PAUSE;
          end
        end

        GET_READ_DATA: begin
          // wait here until the current read command has been ack'd
          // and the data put into the read fifo.
          if (read_command_done) begin
            if (data_count == 3'd0) begin
              rx_state <= RX_IDLE;
            end else begin
              rx_state   <= SEND_COMMAND;
              data_count <= data_count - 3'd1;
            end
            command_done      <= 1'd0;
            read_command_done <= 1'd0;
          end else begin
            rx_state <= GET_READ_DATA;
          end
        end

        RX_ABORT: begin
          lpcb_frame_out <= 1'd1;
          lpcb_cad_out   <= `LPCB_ABORT_CMD;
          rx_state <= RX_IDLE;
        end

        RX_RESET: begin
          lpcb_frame_out <= 1'd1;
          lpcb_cad_out   <= `LPCB_RESET_CMD;
          rx_state <= RX_IDLE;
        end

        default : begin
          rx_state <= RX_IDLE;
        end
      endcase // casez(rx_state)
    end
  end

  /*------------------------------------------------------------------
   *
   * monitor the return interface and stuff any recieved data into the
   * tx fifo.
   *
   * */

  parameter [4:0] // synopsys enum tx_state
               TX_IDLE           = 4'd0,

               GET_DATA          = 4'd1,
               TX_DATA           = 4'd2,

               TX_END            = 4'd15;


  reg [4:0]       /* synopsys enum tx_state */ tx_state;

  /*AUTOASCIIENUM("tx_state", "ascii_tx_state", nul)*/
  // Beginning of automatic ASCII enum decoding
  reg [63:0]            ascii_tx_state;         // Decode of tx_state
  always @(tx_state) begin
    case ({tx_state})
      TX_IDLE:  ascii_tx_state = "tx_idle ";
      GET_DATA: ascii_tx_state = "get_data";
      TX_DATA:  ascii_tx_state = "tx_data ";
      TX_END:   ascii_tx_state = "tx_end  ";
      default:  ascii_tx_state = "%Error  ";
    endcase
  end
  // End of automatics


  reg [3:0]             tx_count;
  reg [31:0]            read_data;

  reg [7:0]             tx_data;
  reg                   tx_data_valid;
  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      tx_state      <= TX_IDLE;
      tx_count      <= 4'd0;
      read_data     <= 32'd0;
      tx_data       <= 8'd0;
      tx_data_valid <= 1'd0;
      command_done_set  <= 1'd0;
      read_command_done_set  <= 1'd0;
    end else begin
      tx_data_valid <= 1'd0;
      command_done_set <= 1'd0;
      read_command_done_set <= 1'd0;
      case(tx_state)
        TX_IDLE: begin
          if (lpcb_frame_in) begin
            if (lpcb_cad_in == `LPCB_READ_ACK_CMD) begin
              tx_count      <= 4'd7;
              tx_state      <= GET_DATA;
            end else begin
              tx_count      <= 4'd0;
              tx_state      <= TX_IDLE;
              // this was some other command that has closed the loop.
              command_done_set  <= 1'd1;
            end
          end else
            tx_state      <= TX_IDLE;
        end

        GET_DATA: begin
          if (lpcb_frame_in) begin
            if (lpcb_cad_in == `LPCB_READ_ACK_CMD) begin
              // abort this read and collect the new one.  This is
              // technically an error but try to be gracefull.
              tx_count      <= 4'd7;
              tx_state      <= GET_DATA;
            end else begin
              // this is the abort or reset comand
              tx_count      <= 4'd0;
              tx_state      <= TX_IDLE;
            end
          end else begin
            case(tx_count[2:0])
              3'd7 : read_data[31:28] <= lpcb_cad_in;
              3'd6 : read_data[27:24] <= lpcb_cad_in;
              3'd5 : read_data[23:20] <= lpcb_cad_in;
              3'd4 : read_data[19:16] <= lpcb_cad_in;
              3'd3 : read_data[15:12] <= lpcb_cad_in;
              3'd2 : read_data[11: 8] <= lpcb_cad_in;
              3'd1 : read_data[ 7: 4] <= lpcb_cad_in;
              3'd0 : read_data[ 3: 0] <= lpcb_cad_in;
            endcase // case(count)
            if (tx_count == 4'd0) begin
              tx_count <= 4'd3;
              tx_state <= TX_DATA;
            end else begin
              tx_count <= tx_count - 4'd1;
              tx_state <= GET_DATA;
            end
          end
        end

        TX_DATA: begin
          // ignoring any incoming commands at this point.
          if (tx_ready & ~tx_data_valid) begin
            tx_data_valid <= 1'd1;
            case(tx_count[1:0])
              3'd3 : tx_data <= read_data[31:24];
              3'd2 : tx_data <= read_data[23:16];
              3'd1 : tx_data <= read_data[15: 8];
              3'd0 : tx_data <= read_data[ 7: 0];
            endcase // case(count)
          end else if (tx_ready & tx_data_valid) begin
            tx_data_valid <= 1'd1;
            if (tx_count == 4'd0) begin
              tx_count <= 4'd0;
              tx_state <= TX_IDLE;
              read_command_done_set <= 1'd1;
            end else begin
              tx_count <= tx_count - 4'd1;
              tx_state <= TX_DATA;
            end
          end else begin
            tx_data_valid <= 1'd0;
            tx_state <= TX_DATA;
          end
        end

        default : tx_state <= TX_IDLE;

      endcase // case(tx_state)
    end
  end

  /*----------------------------------------------------------------*/

endmodule // dcom_lpcb_if


// Local Variables:
// verilog-library-directories:(".")
// verilog-library-extensions:(".v")
// End:
